package com.ym.domain.service.impl;

import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dtflys.forest.utils.StringUtils;
import com.ym.domain.common.base.MedisValue;
import com.ym.domain.common.base.Result;
import com.ym.domain.common.constant.MessageConst;
import com.ym.domain.common.constant.SymbolConst;
import com.ym.domain.common.util.DateComUtil;
import com.ym.domain.common.util.ServletComUtil;
import com.ym.domain.common.util.StringComUtil;
import com.ym.domain.dao.IpBlackListService;
import com.ym.domain.model.IpBlackList;
import com.ym.domain.model.Medis;
import com.ym.domain.model.vo.VerifyCodeVo;
import com.ym.domain.service.MedisBizService;
import com.ym.domain.service.VerifyCodeBizService;
import com.ym.domain.verifycode.VerifyCodeProperties;
import com.ym.domain.verifycode.enums.CaptchaCategory;
import com.ym.domain.verifycode.enums.CaptchaType;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.LocalTime;

@Slf4j
@Service
public class VerifyCodeBizServiceImpl implements VerifyCodeBizService {

    @Value("${app.system.ip.verifyCode.timeOut}")
    private Long verifyCodeTimeOut;

    @Value("${app.system.ip.verifyCode.todayMaxTimes}")
    private Long verifyCodeTodayMaxTimes;

    @Value("${app.system.ip.verifyCode.frequency}")
    private Long frequency;

    @Autowired
    MedisBizService medisBizService;

    @Autowired
    IpBlackListService ipBlackListService;

    @Autowired
    VerifyCodeProperties verificationCodeProperties;


    @Override
    public Result getVerifyCode() {
        VerifyCodeVo verifyCodeVo = new VerifyCodeVo();

        String ip = getRequestIp();

        // 检查IP验证码黑名单
        if (isInMobileBlackList(ip)) {
            return Result.fail(MessageConst.ERR_IP_VERIFY_CODE_BLACKLIST, false);
        }

        // 检查IP验证码当日生成的最大次数
        Medis medis = getMedis(ip);

        if (overIpVerifyCodeMaxTimes(medis)) {
            return Result.fail(MessageConst.ERR_IP_VERIFY_CODE_TODAY_MAX_TIMES, false);
        }

        // 检查验证码发送频率
        if (overVerifyCodeFrequency(medis)) {
            return Result.fail(MessageConst.ERR_IP_VERIFY_CODE_FREQUENTLY_SEND, false);
        }

        // 生成验证码
        CaptchaType captchaType = verificationCodeProperties.getType();
        boolean isMath = CaptchaType.MATH == captchaType;
        Integer length = isMath ? verificationCodeProperties.getNumberLength() : verificationCodeProperties.getCharLength();
        CodeGenerator codeGenerator = ReflectUtil.newInstance(captchaType.getClazz(), length);
        CaptchaCategory category = verificationCodeProperties.getCategory();
        AbstractCaptcha captcha = SpringUtil.getBean(category.getClazz());
        captcha.setGenerator(codeGenerator);
        captcha.createCode();
        String code = captcha.getCode();
        if (isMath) {
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(StringComUtil.remove(code, "="));
            code = exp.getValue(String.class);
        }

        String uuid = IdUtil.simpleUUID();
        log.info("【获取验证码】ip：{}, uuid: {}, code: {}", ip, uuid, code);

        // 保存验证码到缓存
        putVerifyCode(ip, uuid, code);

        verifyCodeVo.setCodeUuid(uuid);
        verifyCodeVo.setCodeImg(captcha.getImageBase64());
        return Result.success(verifyCodeVo);
    }

    private String getRequestIp() {
        HttpServletRequest request = ServletComUtil.getRequest();
        if (request != null) {
            return JakartaServletUtil.getClientIP(request);
        }
        return "未知";
    }

    private boolean isInMobileBlackList(String ip) {
        QueryWrapper<IpBlackList> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(IpBlackList::getIp, ip);
        return ipBlackListService.count(queryWrapper) > 0;
    }

    private Medis getMedis(String ip) {
        String key = DateComUtil.getLocalDateNowYyyyMmDd() + SymbolConst.UNDERLINE + ip;
        return medisBizService.getMedis(key);
    }

    private boolean overIpVerifyCodeMaxTimes(Medis medis) {
        if (null == medis) {
            return false;
        }
        if (StringUtils.isBlank(medis.getCacheValue())) {
            return false;
        }
        int count = Integer.parseInt(medis.getCacheValue());
        return count >= verifyCodeTodayMaxTimes;
    }

    private boolean overVerifyCodeFrequency(Medis medis) {
        if (null == medis) return false;
        if (StringUtils.isBlank(medis.getCacheValue())) return false;
        LocalDateTime updateTime = medis.getUpdateTime();
        Long diffSecond = DateComUtil.getDiffSecondBetweenDate(updateTime, DateComUtil.getLocalDateTimeNow());
        //System.out.println("-----kkkkk 相差时间 : " + diffSecond + ", 限制时长 : " + frequency);
        return diffSecond <= frequency;
    }

    private void putVerifyCode(String ip, String uuid, String code) {
        // 缓存验证码
        String codeKey = ip + SymbolConst.UNDERLINE + uuid;
        medisBizService.put(codeKey, code, verifyCodeTimeOut);

        // 缓存IP调用次数
        String ipKey = DateComUtil.getLocalDateNowYyyyMmDd() + SymbolConst.UNDERLINE + ip;

        MedisValue medisValue = medisBizService.get(ipKey);
        long expireTime = getExpireTime();
        int count = 0;
        if (StringUtils.isNotBlank(medisValue.getValue())) {
            count = Integer.parseInt(medisValue.getValue());
        }
        medisBizService.put(ipKey, String.valueOf(++count), expireTime);
    }

    private long getExpireTime() {
        LocalDateTime startTime = DateComUtil.getLocalDateTimeNow();
        LocalDateTime endTime = DateComUtil.getLocalDateTimeNow().plusDays(1).with(LocalTime.MIN);
        return DateComUtil.getDiffSecondBetweenDate(startTime, endTime);
    }

    @Override
    public MedisValue getVerifyCode(String uuid) {
        String codeKey = getRequestIp() + SymbolConst.UNDERLINE + uuid;
        return medisBizService.get(codeKey);
    }


    public static void main(String[] args) {

    }


}
